/**    
 * 文件名：PaymentRequestHandler.java</br>
 *    
 * 版本信息：v1.0</br>
 * 日期：2017年7月7日</br>
 * © 2005-2017 雷技信息科技（上海）有限公司版权所有</br>
 *
 */
package com.logic.landseaserver.models.payment.service;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.logic.common.security.SecurityUtils;
import com.logic.landseaserver.common.exception.LandseaException;
import com.logic.landseaserver.common.exception.ResultCodes.PaymentResultCode;
import com.logic.landseaserver.common.util.CommonUtils;
import com.logic.landseaserver.domain.BillPayment;
import com.logic.landseaserver.models.payment.BillPaymentCollections;
import com.logic.landseaserver.models.payment.PaymentConstant;
import com.logic.landseaserver.models.payment.PaymentConstant.BillPaymentStatus;
import com.logic.landseaserver.models.payment.bean.PaymentHelperBean;
import com.logic.landseaserver.models.payment.bean.PaymentRequest;
import com.logic.landseaserver.models.payment.channel.AbstractPaymentChannel;
import com.logic.system.AppContext;

/**
 *
 * 项目名称：system-server</br>
 * 类名称：AbstractPaymentRequestHandler</br>
 * 类描述：支付处理</br>
 * 创建人：Aaron</br>
 * 创建时间：2017年7月7日 下午4:37:28</br>
 * 
 * @version 1.0
 *
 */
public abstract class AbstractPaymentRequestHandler
{
    protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractPaymentRequestHandler.class);
    
    @Autowired
    private AppContext appContext;
    
    /**
     * 
     * [简要描述]：支付</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-07)</br>
     *
     */
    public Map<String, Object> doPay(PaymentRequest payment)
        throws LandseaException
    {
        if (null == payment)
        {
            throw LandseaException.createException(PaymentResultCode.E00140001);
        }
        String logInfo = getLog(payment, null);
        try
        {
            LOGGER.info(logInfo + " Start|Request|" + payment.toString());
            
            // 自定义中间传递对象，用来承载每一步处理的输出
            PaymentHelperBean helperObject = new PaymentHelperBean();
            LOGGER.info(logInfo + " Build helper object has been finished.");
            
            // 公共参数校验
            commonCheckPayParam(payment, helperObject);
            LOGGER.info(logInfo + " Check pay common parameter has been finished.");
            
            // 自定义检查参数合法性
            customCheckPayParam(payment, helperObject);
            LOGGER.info(logInfo + " Check pay custom parameter has been finished.");
            
            // 检查金额合法性(从账单及已支付的金额核算)
            checkPayBillAndMoney(payment, helperObject);
            LOGGER.info(logInfo + " Check pay bill and money has been finished.");
            
            // 开始处理业务
            LOGGER.info(logInfo + " Start process service.");
            
            // 结果集，如果有需要返回的，放入该集合
            Map<String, Object> resultMap = new HashMap<String, Object>();
            
            // 生成billPayment
            BillPayment billPayment = generateBillPayment(payment);
            
            doBusiness(billPayment, helperObject, resultMap);
            
            LOGGER.info(logInfo + " Process service end.paymentId=" + billPayment.getId());
            return resultMap;
        }
        catch (LandseaException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            LOGGER.info(logInfo + "| Do pay failed.", e);
            throw LandseaException.createException(PaymentResultCode.E00150001, e);
        }
    }
    
    /**
     * 
     * [简要描述]：构造billPayment对象</br>
     * [详细描述]：不同的支付渠道可能不同</br>
     * [作者]：Aaron(2017-07-19)</br>
     *
     * @param payment
     * @return
     * @throws LandseaException
     *
     */
    public BillPayment generateBillPayment(PaymentRequest payment)
        throws LandseaException
    {
        // 根据支付渠道调用不同的生成方法
        String beanId = PaymentConstant.GENETRATE_BILLPAYMENT_PREFIX + payment.getPayChannel();
        AbstractPaymentChannel paymentChannel = (AbstractPaymentChannel)appContext.getBean(beanId);
        
        String logInfo = getLog(payment, null);
        if (null == paymentChannel)
        {
            LOGGER.error(logInfo + "|get paymentChannel failed,beanId=" + beanId);
            throw LandseaException.createException(PaymentResultCode.E00140003);
        }
        
        BillPayment billPayment = paymentChannel.buildBillPayment(payment);
        if (null == billPayment)
        {
            LOGGER.error(logInfo + "|build billPayment object is null");
            throw LandseaException.createException(PaymentResultCode.E00150001);
        }
        
        // 添加公共字段
        billPayment.setBillId(payment.getBillId());
        billPayment.setBillType(payment.getBillType());
        billPayment.setPayType(payment.getPayType());
        // 默认未支付
        billPayment.setStatus(BillPaymentStatus.NONE);
        billPayment.updateCommonInfo(SecurityUtils.getCurrentLogin().getUserId());
        
        return billPayment;
    }
    
    /**
     * 
     * [简要描述]：开始处理支付业务</br>
     * [详细描述]：事务已添加，子类无需添加事务注解</br>
     * [作者]：Aaron(2017-07-07)</br>
     *
     * @param payment 支付对象
     * @param helperObject 帮助对象
     * @throws LandseaException 异常
     *
     */
    public void doBusiness(BillPayment billPayment, PaymentHelperBean helperObject, Map<String, Object> resultMap)
        throws LandseaException
    {
        String logInfo = getLog(billPayment, null);
        try
        {
            // 本地业务处理
            doLocalBusiness(billPayment, helperObject, resultMap);
            
            // 获取paymentId
            Integer paymentId = getPaymentId(billPayment, helperObject);
            logInfo = getLog(billPayment, paymentId);
            
            // 向第三方发送消息
            sendPayRequest(billPayment, helperObject, resultMap);
        }
        catch (LandseaException e)
        {
            // 自定义异常直接抛出
            throw e;
        }
        catch (Exception e)
        {
            LOGGER.info(logInfo + "| Do pay business failed.", e);
            throw LandseaException.createException(PaymentResultCode.E00150001, e);
        }
    }
    
    /**
     * 
     * [简要描述]：获取支付日志打印前缀</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @return log info
     *
     */
    protected String getLog(BillPayment payment, Integer paymentId)
    {
        String billTypeName = BillPaymentCollections.getBillTypeName(payment.getBillType());
        String payType = payment.getPayType();
        String messageId = getMessageId(payType, String.valueOf(payment.getBillId()), paymentId);
        
        // 规则：账单类型|支付方式|处理类名称|消息Id|
        StringBuilder logStringBuilder = new StringBuilder();
        logStringBuilder.append(billTypeName);
        logStringBuilder.append(payType);
        logStringBuilder.append("|");
        logStringBuilder.append(getProcessClassName());
        logStringBuilder.append("|");
        logStringBuilder.append(messageId);
        logStringBuilder.append("|");
        
        return logStringBuilder.toString();
    }
    
    /**
     * 
     * [简要描述]：获取支付日志打印前缀</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @return log info
     *
     */
    protected String getLog(PaymentRequest payment, Integer paymentId)
    {
        String billTypeName = BillPaymentCollections.getBillTypeName(payment.getBillType());
        String payType = payment.getPayType();
        String messageId = getMessageId(payType, String.valueOf(payment.getBillId()), paymentId);
        
        // 规则：账单类型|支付方式|处理类名称|消息Id|
        StringBuilder logStringBuilder = new StringBuilder();
        logStringBuilder.append(billTypeName);
        logStringBuilder.append("|");
        logStringBuilder.append(payType);
        logStringBuilder.append("|");
        logStringBuilder.append(getProcessClassName());
        logStringBuilder.append("|");
        logStringBuilder.append(messageId);
        logStringBuilder.append("|");
        
        return logStringBuilder.toString();
    }
    
    /**
     * 
     * [简要描述]：</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @param payType 支付类型
     * @param billId 账单id
     * @param paymentId 支付id
     * @return 消息id
     *
     */
    protected String getMessageId(String payType, String billId, Integer paymentId)
    {
        // 作用：根据messageId可以快速的跟踪一次支付从开始到回调的全过程，方便定位问题
        
        // 如果还未生成paymentId
        if (null == paymentId)
        {
            // 规则：payType_billId
            return payType + PaymentConstant.UNDERLINE_SEPARATOR + billId;
        }
        
        // 规则: payType_billId_paymentId
        return payType + PaymentConstant.UNDERLINE_SEPARATOR + billId + PaymentConstant.UNDERLINE_SEPARATOR + paymentId;
    }
    
    /**
     * 
     * [简要描述]：检查参数合法性</br>
     * [详细描述]：必填校验，值校验，逻辑校验</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @param payment
     * @param helperObject
     * @throws LandseaException
     *
     */
    protected void commonCheckPayParam(PaymentRequest payment, PaymentHelperBean helperObject)
        throws LandseaException
    {
        // 账单ID
        CommonUtils.checkEmpty(payment.getBillId(), PaymentResultCode.E00140002);
        
        // 支付渠道
        String payChannel = payment.getPayChannel();
        CommonUtils.checkEmpty(payChannel, PaymentResultCode.E00140003);
        if (!BillPaymentCollections.payChannelExists(payChannel))
        {
            throw LandseaException.createException(PaymentResultCode.E00140003);
        }
        
        // 支付类型
        String payType = payment.getPayType();
        CommonUtils.checkEmpty(payType, PaymentResultCode.E00140004);
        if (!BillPaymentCollections.payTypeExists(payType))
        {
            throw LandseaException.createException(PaymentResultCode.E00140003);
        }
        
        // 账单类型
        String billType = payment.getBillType();
        CommonUtils.checkEmpty(billType, PaymentResultCode.E00140005);
        String billTypeName = BillPaymentCollections.getBillTypeName(billType);
        if (null == billTypeName)
        {
            // 账单类型非法
            throw LandseaException.createException(PaymentResultCode.E00140005);
        }
    }
    
    /**
     * 
     * [简要描述]：向第三方发送消息</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @param payment
     * @param helperObject
     * @throws LandseaException
     *
     */
    protected void sendPayRequest(BillPayment billPayment, PaymentHelperBean helperObject, Map<String, Object> resultMap)
        throws LandseaException
    {
        // 根据支付渠道发送消息
        String beanId =
            PaymentConstant.GENETRATE_BILLPAYMENT_PREFIX + BillPaymentCollections.getPayChannelByPayType(billPayment.getPayType());
        
        AbstractPaymentChannel paymentChannel = (AbstractPaymentChannel)appContext.getBean(beanId);
        
        String logInfo = getLog(billPayment, null);
        if (null == paymentChannel)
        {
            LOGGER.error(logInfo + "get paymentChannel failed,beanId=" + beanId);
            throw LandseaException.createException(PaymentResultCode.E00140003);
        }
        
        LOGGER.info(logInfo + "sendPayRequest start.");
        paymentChannel.sendPayRequest(billPayment, helperObject, resultMap);
    }
    
    /**
     * 
     * [简要描述]：自定义检查参数合法性</br>
     * [详细描述]：必填校验，值校验，逻辑校验</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @param payment
     * @param helperObject
     * @throws LandseaException
     *
     */
    protected abstract void customCheckPayParam(PaymentRequest payment, PaymentHelperBean helperObject)
        throws LandseaException;
    
    /**
     * 
     * [简要描述]：检查账单及金额</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @param payment
     * @param helperObject
     * @throws LandseaException
     *
     */
    protected abstract void checkPayBillAndMoney(PaymentRequest payment, PaymentHelperBean helperObject)
        throws LandseaException;
    
    /**
     * 
     * [简要描述]：支付本地业务</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @param payment
     * @throws LandseaException
     *
     */
    protected abstract void doLocalBusiness(BillPayment payment, PaymentHelperBean helperObject, Map<String, Object> resultMap)
        throws LandseaException;
    
    /**
     * 
     * [简要描述]：获取支付记录表的id</br>
     * [详细描述]：payService中入库后已经生成,请使用helperObject携带</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @return
     *
     */
    protected abstract Integer getPaymentId(BillPayment billPayment, PaymentHelperBean helperObject);
    
    /**
     * 
     * [简要描述]：获取处理类</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-12)</br>
     *
     * @return
     *
     */
    protected abstract String getProcessClassName();
}
